package com.study.crypto.gb.server.advice;

import com.alibaba.fastjson.JSON;
import com.study.crypto.dto.SignInfoDto;
import com.study.crypto.dto.gb.RequestPoliceDto;
import com.study.crypto.dto.gb.base.RequestGbDto;
import com.study.crypto.general.spring.annotation.RequireVerify;
import com.study.crypto.gb.server.entity.Certification;
import com.study.crypto.gb.server.exception.CertificateException;
import com.study.crypto.gb.server.exception.GbRuntimeException;
import com.study.crypto.gb.server.mapper.CertificationMapper;
import com.study.crypto.signer.Signer;
import com.study.crypto.signer.SignerFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.pqc.legacy.math.linearalgebra.ByteUtils;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import java.io.IOException;
import java.lang.reflect.Type;
import java.math.BigInteger;
import java.security.GeneralSecurityException;

/**
 * 请求参数进行验签通知，参数使用了 @RequireVerify 注解
 * @author Songjin
 * @since 2021-06-11 12:59
 */
@Slf4j
@ControllerAdvice
public class RequestBodyVerifyAdvice extends RequestBodyAdviceAdapter {

    private final Signer signer = SignerFactory.produce(GMObjectIdentifiers.sm2sign_with_sm3);
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private CertificationMapper certificationMapper;
    
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return methodParameter.hasParameterAnnotation(RequireVerify.class);
    }
    
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        log.info("RequestBodyVerifyAdvice.afterBodyRead==========>begin");
        boolean instanceof1 = body instanceof RequestGbDto;
        boolean instanceof2 = body instanceof RequestPoliceDto;
        if (instanceof1 || instanceof2) {
            String signInfoProperty = "signInfo";
            BeanWrapperImpl beanWrapper = new BeanWrapperImpl(body);
            SignInfoDto signInfo = (SignInfoDto) beanWrapper.getPropertyValue(signInfoProperty);
            if (log.isInfoEnabled()) {
                log.info("参数验签，签名数据: {}", JSON.toJSONString(signInfo));
            }
            beanWrapper.setPropertyValue(signInfoProperty, null);
    
            String digest, taskCode;
            if (instanceof2) {
                String taskId = ((RequestPoliceDto) body).getData().getTaskId();
                digest = redisTemplate.opsForValue().get(taskId);
                taskCode = ((RequestPoliceDto) body).getTaskCode();
            } else {
                // 1.进行随机数校验
                String tokenInfo = ((RequestGbDto) body).getTokenInfo();
                digest = redisTemplate.opsForValue().get(tokenInfo);
                taskCode = ((RequestGbDto) body).getTaskCode();
            }
            if (StringUtils.isBlank(digest)) {
                throw new GbRuntimeException(901011, taskCode, "验证随机数失败");
            }
            Certification query = Certification.builder().digest(digest).build();
            Certification certification = certificationMapper.selectOne(query);
            if (certification == null) {
                throw new CertificateException("证书未注册");
            }

            try {
                // 2.进行参数验签
                Certificate certificate = Certificate.getInstance(certification.getCertificate());
                byte[] inData = JSON.toJSONBytes(body);
                assert signInfo != null;
                byte[] signature = Base64.decodeBase64(signInfo.getSignValue());
                byte[] r = ByteUtils.subArray(signature, 0, 32);
                byte[] s = ByteUtils.subArray(signature, 32, 64);
                ASN1EncodableVector vector = new ASN1EncodableVector();
                vector.add(new ASN1Integer(new BigInteger(1, r)));
                vector.add(new ASN1Integer(new BigInteger(1, s)));
                DERSequence sequence = new DERSequence(vector);
                boolean verify = signer.verify(inData, sequence.getEncoded(), certificate);
                if (verify) {
                    beanWrapper.setPropertyValue(signInfoProperty, signInfo);
                    log.info("RequestBodyVerifyAdvice.afterBodyRead==========>end");
                    return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
                } else {
                    throw new GbRuntimeException(901014, taskCode, "验签失败");
                }
            } catch (NullPointerException | IOException | GeneralSecurityException e) {
                log.error("验签异常", e);
                throw new GbRuntimeException(901014, taskCode, "验签失败");
            }
        }
        log.info("RequestBodyVerifyAdvice.afterBodyRead==========>end");
        return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
    }
}
